﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using Microsoft.Win32;
using PSE.Updater.Common.Configurations.Client;
using PSE.Framework.ErrorLogging;

namespace PSE.Updater.Client.Engine
{
    /// <summary>
    /// Entidade que representa os detalhes de um sistema
    /// </summary>
    public class SystemDetails
    {
        private const string CONFIG_FILE_EXTENSION        = "*.config";

        private const string REGISTRY_SYSTEM_ROOT_PATH    = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{0}";
        private const string REGISTRY_SYSTEM_MODULES_PATH = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{0}\Modules";
        private const string REGISTRY_KEY_VERSION         = "Version";
        private const string REGISTRY_KEY_DISPLAY_VERSION = "DisplayVersion";

        public SystemDetails()
        {

        }

        /// <summary>
        /// Metodo utilizado para gerar o nome da pasta no registro para um produto e sua major version
        /// </summary>
        /// <param name="systemName">Nome do sistema</param>
        /// <param name="majorVersion">Major Version do Produto</param>
        /// <returns>Nome da pasta no registro para um produto e sua major version</returns>
        private string CombineSystemNameAndMajorVersion(string systemName, int majorVersion)
        {
            return string.Format("{0}-v{1}", systemName, majorVersion);
        }

        /// <summary>
        /// Construtor da Entidade responsável por carregar a lista de Módulos e Dependências
        /// </summary>
        /// <param name="systemName">Nome do sistema</param>
        /// <param name="majorVersion">Versão do Sistema</param>
        /// <param name="loadDependencies">Indica se carrega as dependências ou não</param>
        public SystemDetails(string systemName, int majorVersion, bool loadDependencies)
        {
            Initialize(systemName, majorVersion, loadDependencies, string.Empty);
        }

        /// <summary>
        /// Construtor da Entidade responsável por carregar a lista de Módulos e Dependências
        /// </summary>
        /// <param name="systemName">Nome do sistema</param>
        /// <param name="majorVersion">Versão do Sistema</param>
        /// <param name="loadDependencies">Indica se carrega as dependências ou não</param>
        /// <parparam name="installationPath">Caminho onde o sistema foi instalado</parparam>
        public SystemDetails(string systemName, int majorVersion, bool loadDependencies, string installationPath)
        {
            Initialize(systemName, majorVersion, loadDependencies, installationPath);
        }

        /// <summary>
        /// Inicializa as configurações do Sistema
        /// </summary>
        /// <param name="systemName">Nome do sistema</param>
        /// <param name="majorVersion">Versão do Sistema</param>
        /// <param name="loadDependencies">Indica se carrega as dependências ou não</param>
        /// <parparam name="installationPath">Caminho onde o sistema foi instalado</parparam>
        public void Initialize(string systemName, int majorVersion, bool loadDependencies, string installationPath)
        {
            if (string.IsNullOrEmpty(systemName))
                throw new ConfigurationErrorsException("Nome do Sistema não informado");

            if (majorVersion <= 0)
                throw new Exception("Invalid Major Version");

            this.SystemName = systemName;

            this.Version = GetSystemDisplayVersion(string.Format(REGISTRY_SYSTEM_ROOT_PATH, CombineSystemNameAndMajorVersion(systemName, majorVersion)));

            this.Modules = GetModules(string.Format(REGISTRY_SYSTEM_MODULES_PATH, CombineSystemNameAndMajorVersion(systemName, majorVersion)));

            if (!string.IsNullOrEmpty(installationPath))
                this.ConfigFiles = ListConfigFiles(installationPath);

            //if (loadDependencies)
            //    this.Dependencies = GetDependencies();
        }

        private List<string> ListConfigFiles(string installationPath)
        {
            var list = System.IO.Directory.GetFiles(installationPath, CONFIG_FILE_EXTENSION, System.IO.SearchOption.AllDirectories);

            return list.ToList();
        }

        /// <summary>
        /// Obtém um valor de uma chave no registro
        /// </summary>
        /// <param name="path">Caminho da Pasta</param>
        /// <param name="valueName">Nome da Chave</param>
        /// <returns>Valor de uma chave no registro</returns>
        private string GetValue(string path, string keyName)
        {
            if (string.IsNullOrEmpty(path))
                throw new ConfigurationErrorsException("Caminho do Registro não informado");

            if (string.IsNullOrEmpty(keyName))
                throw new ConfigurationErrorsException("Chave do Registro não infoamada");

            var key = Registry.LocalMachine.OpenSubKey(path);
            if (key == null)
                throw new ConfigurationErrorsException(string.Format("Não fói possível encontrar o caminho {0} no Registro", path));

            object @value = key.GetValue(keyName);
            if (@value == null)
                throw new ConfigurationErrorsException(string.Format("Não fói possível encontrar a chave {0} no caminho {1} do Registro", keyName, path));

            return @value.ToString();
        }

        /// <summary>
        /// Retorna todas as pastas dentro de uma determinada pasta
        /// </summary>
        /// <param name="path">Caminho da pasta</param>
        /// <returns>Todas as pastas dentro de uma determinada pasta</returns>
        private IList<string> GetFolders(string path)
        {
            try
            {
                if (string.IsNullOrEmpty(path))
                    throw new ConfigurationErrorsException("Caminho do Registro não informado");

                var key = Registry.LocalMachine.OpenSubKey(path);
                if (key == null)
                    throw new Exception(string.Format("Não fói possível encontrar o caminho {0} no Registro", path));

                return key.GetSubKeyNames();

            }
            catch (Exception ex)
            {
                ErrorLogger.WriteLog(LogType.Error, ex);
            }
            return null;
        }

        /// <summary>
        /// Obtém a versão de um determinado Sistema
        /// </summary>
        /// <param name="path">Caminho da pasta do sistema</param>
        /// <returns>A versão de um determinado Sistema</returns>
        private string GetSystemVersion(string path)
        {
            return GetValue(path, REGISTRY_KEY_VERSION);
        }


        /// <summary>
        /// Obtém a versão de um determinado Sistema
        /// </summary>
        /// <param name="path">Caminho da pasta do sistema</param>
        /// <returns>A versão de um determinado Sistema</returns>
        private string GetSystemDisplayVersion(string path)
        {
            return GetValue(path, REGISTRY_KEY_DISPLAY_VERSION);
        }

        /// <summary>
        /// Retorna todos os módulos do Sistema
        /// </summary>
        /// <param name="path">Caminho da pasta do sistema</param>
        /// <returns>Todos os módulos do Sistema</returns>
        private List<ModuleDetails> GetModules(string path)
        {
            List<ModuleDetails> modules = new List<ModuleDetails>();
            
            IList<string> folders = GetFolders(path);
            if (folders == null || folders.Count == 0)
                return modules;

            foreach (string folder in folders)
            {
                ModuleDetails module = new ModuleDetails();
                module.Version = GetSystemVersion(string.Format(@"{0}\{1}", path, folder));
                module.ModuleName = folder;

                modules.Add(module);
            }
            return modules;
        }

        ///// <summary>
        ///// Retorna todas as dependências do sistema
        ///// </summary>
        ///// <returns>Todas as dependências do sistema</returns>
        //private List<SystemDetails> GetDependencies()
        //{
        //    UpdateConfigurationSection section = UpdateConfiguration.GetInformation();
        //    if (section == null)
        //        throw new Exception("Não foi possível carregar as configurações de sistema");

        //    this.ServerUrl = section.DefaultUpdateServerUrl;

        //    if (section.SystemDependencies == null)
        //        return null;

        //    List<SystemDetails> dependencies = new List<SystemDetails>();
            
        //    List<SystemDetails> defaulDependencies = GetDefaultDependencies();
        //    if (defaulDependencies != null &&  defaulDependencies.Count > 0)
        //        dependencies.AddRange(defaulDependencies);
        //    for (int i = 0; i < section.SystemDependencies.Count; i++)
        //    {
        //        var dependence = section.SystemDependencies[i];

        //        IList<string> version = dependence.Version.Split('.');
        //        if (version.Count == 0)
        //            throw new ConfigurationErrorsException(string.Format("Versão da Dependência {0} inválida", dependence.SystemName));

        //        int majorVersion = Convert.ToInt32(version[0]);
        //        //string productId = string.Format("{0}-v{1}", dependence.Name, majorVersion);

        //        SystemDetails detail = new SystemDetails(dependence.SystemName, majorVersion, false);
        //        detail.SystemName = dependence.SystemName;
        //        detail.Version = dependence.Version;
        //        detail.Index = dependence.Index;

        //        dependencies.Add(detail);
        //    }
        //    return dependencies;
        //}

        /// <summary>
        /// Retorna todas as dependências default do sistema - (PSE e PSEComponents)
        /// </summary>
        /// <returns>Todas as dependências do sistema</returns>
        private List<SystemDetails> GetDefaultDependencies()
        {
            return null;
        }

        /// <summary>
        /// Obtem um determinado módulo, atualizado, do registro
        /// </summary>
        /// <param name="productId">Id do produto</param>
        /// <param name="majorVersion">Versão do Produto</param>
        /// <param name="moduleId">Código do Módulo</param>
        /// <returns>Um determinado módulo, atualizado, do registro</returns>
        public ModuleDetails GetModuleVersion(string systemName, int majorVersion, string moduleId)
        {
            string folder = CombineSystemNameAndMajorVersion(systemName, majorVersion);
            string path = string.Format(REGISTRY_SYSTEM_MODULES_PATH, folder);
            List<ModuleDetails> modules = GetModules(path);
            ModuleDetails module = modules.FirstOrDefault(m => m.ModuleName.Equals(moduleId));
            if (module == null)
                throw new ConfigurationErrorsException(string.Format("Módulo {0} não encontrado", moduleId));

            return module;
        }

        /// <summary>
        /// Nome do Sistema
        /// </summary>
        public string SystemName { get; private set; }

        /// <summary>
        /// Indice da Instalação
        /// </summary>
        public int Index { get; set; }

        /// <summary>
        /// Versão do Sistema
        /// </summary>
        public string Version { get; set; }

        /// <summary>
        /// Url do Servidor de Atualização
        /// </summary>
        public string ServerUrl { get; set; }

        /// <summary>
        /// Módulos Instalados do Sistema
        /// </summary>
        public List<ModuleDetails> Modules { get; private set; }

        /// <summary>
        /// Dependencias do sistema
        /// </summary>
        public List<SystemDetails> Dependencies { get; private set; }

        /// <summary>
        /// Arquivos de Configuração do sistema
        /// </summary>
        public List<string> ConfigFiles { get; private set; }
    }
}